package com.gitee.l0km.codegen.thrift;

import org.apache.commons.configuration2.Configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.gitee.l0km.codegen.annotations.DeriveMethod;
import com.gitee.l0km.codegen.annotations.ServicePort;
import com.gitee.l0km.xthrift.base.metadata.PatternFilter;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

import static com.gitee.l0km.com4j.base.MiscellaneousUtils.*;
import static com.google.common.base.Preconditions.*;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

/**
 * 根据配置文件提供的过滤参数对接口中的方法进行过滤
 * @author guyadong
 *
 */
public class MethodFilter implements Predicate<Method>{
	private static final Logger logger = LoggerFactory.getLogger(MethodFilter.class);
	private static final String SUFFIX_INCLUDE =".include";
	private static final String SUFFIX_EXCLUDE =".exclude";

	private final Configuration configuration = ThriftServiceDecoratorConfiguration.INSTANCE.getConfig();
	private Set<Method> includeMethods;
	private Set<Method> excludeMethods;
	public MethodFilter(Class<?> inputClass) {
		checkArgument(inputClass != null,"inputClass is null");
		ImmutableMap<String, Method> methods = Maps.uniqueIndex(Arrays.asList(inputClass.getDeclaredMethods()), new Function<Method, String>() {

			@Override
			public String apply(Method input) {
				StringBuffer buffer = new StringBuffer(input.getName());
				DeriveMethod dm = input.getAnnotation(DeriveMethod.class);
				if(dm != null && dm.methodSuffix().length > 0){
					buffer.append('.').append(dm.methodSuffix()[0]);
				}else {
					ServicePort sp = input.getAnnotation(ServicePort.class);
					if(sp != null && sp.suffix().length() > 0){
						buffer.append('.').append(sp.suffix());
					}
				}
				return buffer.toString();
			}
		});
		String includeKey = inputClass.getName() + SUFFIX_INCLUDE;
		String excludeKey = inputClass.getName() + SUFFIX_EXCLUDE;
		includeMethods = getMethodList(includeKey,inputClass,methods,false);
		excludeMethods = getMethodList(excludeKey,inputClass,methods,true);
		if(excludeMethods.isEmpty()){
			excludeMethods = getExcludeMethodList(inputClass,methods);
		}
		if(includeMethods.isEmpty()){
			includeMethods = getIncludeMethodList(inputClass,methods);
		}
		if(!includeMethods.isEmpty() && !excludeMethods.isEmpty()){
			logger.warn("{} and {} all defined, this first used preferentially",includeKey,excludeKey);
		}
	}
	private List<Method> findMethod(String pattern,Map<String, Method> methods,boolean startsWithAsTrue){
		List<Method> found = new ArrayList<>();		
		for(Entry<String, Method> entry:methods.entrySet()){
			String key = entry.getKey();
			String port = key;
			if(key.indexOf(".") >= 0){
				port=Joiner.on("").join(key.split("\\."));
			}
			if(PatternFilter.filter(pattern, port,startsWithAsTrue)){
				found.add(entry.getValue());
			}
		}
		return found;
	}
	private Set<Method> getMethodList(String key,Class<?> interfaceClass,Map<String, Method> methods,boolean startsWithAsTrue){
		Set<Method> sets = Sets.newHashSet();
		if(configuration.containsKey(key)){
			for(String name:elementsOf(configuration.getString(key))){
				List<Method> found = findMethod(name,methods,startsWithAsTrue);
				if(found.isEmpty()){
					logger.warn("NOT FOUND Method named '{}' in {}",name,interfaceClass.getName());				
					continue;
				}			
				sets.addAll(found);
			}
		}
		return sets;
	}
	private Set<Method> getExcludeMethodList(Class<?> interfaceClass,Map<String, Method> methods){
		Set<Method> sets = Sets.newHashSet();
		List<String> names = ThriftServiceDecoratorConfiguration.INSTANCE.getExcludeMethods().get(interfaceClass);
		if(names != null){
			for(String name:names){
				List<Method> found = findMethod(name,methods,true);
				if(found.isEmpty()){
					logger.warn("NOT FOUND Method named '{}' in {}",name,interfaceClass.getName());				
					continue;
				}			
				sets.addAll(found);
			}
		}
		return sets;
	}
	private Set<Method> getIncludeMethodList(Class<?> interfaceClass,Map<String, Method> methods){
		Set<Method> sets = Sets.newHashSet();
		List<String> names = ThriftServiceDecoratorConfiguration.INSTANCE.getIncludeMethods().get(interfaceClass);
		if(names != null){
			for(String name:names){
				List<Method> found = findMethod(name,methods,false);
				if(found.isEmpty()){
					logger.warn("NOT FOUND Method named '{}' in {}",name,interfaceClass.getName());				
					continue;
				}			
				sets.addAll(found);
			}
		}
		return sets;
	}
	@Override
	public boolean apply(Method input) {
		if(!includeMethods.isEmpty()){
			return includeMethods.contains(input);
		}
		if(!excludeMethods.isEmpty()){
			return !excludeMethods.contains(input);
		}
		return true;
	}
	
	
}
